#include <unistd.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avcodec.h>
#include <libavutil/frame.h>

#include <SDL/SDL.h>

#include "video.h"

volatile int g_frame_num = 0;
volatile int g_frame_rate;

static AVFormatContext *fmt_ctx;
static int video_stream_index = -1;
static AVCodecContext *dec_ctx;

static AVPacket packet;
static AVFrame *frame;

static SDL_Surface *pscreen;
static SDL_Overlay *poverlay;

static int width;
static int height;
static char file_name[256];

static int open_input_file(const char *filename) {
    int ret;
    AVCodec *dec;

    if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
        return ret;
    }

    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
        return ret;
    }

    /* select the video stream */
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR,
                "Cannot find a video stream in the input file\n");
        return ret;
    }
    video_stream_index = ret;
    dec_ctx = fmt_ctx->streams[video_stream_index]->codec;

    /* init the video decoder */
    if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");
        return ret;
    }

    return 0;
}

static void sdl_update_title(int num) {
    char pnum[256 + 10];
    if (num == 0) {
        sprintf(pnum, "%s", file_name);
    } else {
        sprintf(pnum, "%s, #%d", file_name, num);
    }
    SDL_WM_SetCaption(pnum, NULL);
}

static int SDL_init(int width, int height) {
    atexit(SDL_Quit);

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        return -1;
    }
    pscreen = SDL_SetVideoMode(width, height, 24, SDL_SWSURFACE);
    if (pscreen == NULL) {
        return -1;
    }
    poverlay = SDL_CreateYUVOverlay(width, height, SDL_YV12_OVERLAY, pscreen);
    if (poverlay == NULL) {
        return -1;
    }

    sdl_update_title(0);

    return 0;
}

static int flip(int width, int height, int num, AVFrame *frame) {
    unsigned char *op[3];
    int y;
    SDL_Rect rect;
    rect.w = width;
    rect.h = height;
    rect.x = 0;
    rect.y = 0;
    SDL_LockSurface(pscreen);
    SDL_LockYUVOverlay(poverlay);

    for (y = 0; y < pscreen->h && y < poverlay->h; y++) {
        op[0] = poverlay->pixels[0] + poverlay->pitches[0] * y;
        op[1] = poverlay->pixels[1] + poverlay->pitches[1] * (y / 2);
        op[2] = poverlay->pixels[2] + poverlay->pitches[2] * (y / 2);
        memcpy(op[0], frame->data[0] + y * frame->linesize[0], width);
        if (y % 2 == 0) {
            memcpy(op[2], frame->data[1] + (y / 2) * frame->linesize[1],
                    width / 2);
            memcpy(op[1], frame->data[2] + (y / 2) * frame->linesize[2],
                    width / 2);
        }
    }
    SDL_UnlockYUVOverlay(poverlay);
    SDL_UnlockSurface(pscreen);
    SDL_DisplayYUVOverlay(poverlay, &rect);

    sdl_update_title(num);

    return 0;
}

int video_open(char *fname) {
    int ret;

    frame = av_frame_alloc();
    if (!frame) {
        perror("Could not allocate frame");
        return -1;
    }

    avcodec_register_all();
    av_register_all();

    if ((ret = open_input_file(fname)) < 0) {
        video_close();
        return -1;
    }

    strcpy(file_name, fname);

    width = fmt_ctx->streams[video_stream_index]->codec->width;
    height = fmt_ctx->streams[video_stream_index]->codec->height;
    g_frame_rate = fmt_ctx->streams[video_stream_index]->r_frame_rate.num;
    if (g_frame_rate > 1000) {
        g_frame_rate = (g_frame_rate + 1) / 100;
    }
    SDL_init(width, height);

    printf("video: %dx%d@%ffps\n", width, height,  (float)g_frame_rate);

    return 0;
}

void video_close(void) {
    if (dec_ctx) {
        // TODO closing avcodec will introduce section error
        //avcodec_close(dec_ctx);
    }
    avformat_close_input(&fmt_ctx);
    av_frame_free(&frame);
}

int video_play_frame() {

    int got_frame;
    int ret;

    if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
        return -1;

    if (packet.stream_index == video_stream_index) {
        avcodec_get_frame_defaults(frame);
        got_frame = 0;
        ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, &packet);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error decoding video\n");
            return -2;
        }

        if (got_frame) {
            flip(width, height, g_frame_num, frame);
            //SDL Event handler
            SDL_Event sdl_event;
            while (SDL_PollEvent(&sdl_event)) {
                switch (sdl_event.type) {
                case SDL_KEYDOWN:
                    break;
                case SDL_KEYUP:
                    if (sdl_event.key.keysym.sym == SDLK_q) {
                    }
                    break;
                case SDL_QUIT:
                    printf("OnQuit\n");
                    ret = -3;
                    goto PLAY_FRAME_END;
                default:
                    break;
                }
            }
        }
    }
    ret = 0;
    PLAY_FRAME_END: av_free_packet(&packet);

    return ret;
}

